package netGaming;

import java.io.IOException;
import java.net.*;

import player.*;
import gameBasicElements.*;
import gameEngine.GameInfo;
import gameEngine.GameStatus;

/**
 * @author Wojciech Łopata
 *
 */

public class NetGame implements GameInfo
{
	private Socket host;
	
	private final MovementSource mvSource;
	private ClientDataSender dataSender;
	
	private volatile GameStatus status;
	private volatile boolean moveRequestActive;
	private volatile GameInfo currentTurnInfo;
	
	private void estabilishConnection(String hostName, int port) throws UnknownHostException, IOException
	{
		host = new Socket(InetAddress.getByName(hostName), port);
		dataSender = new ClientDataSender(host.getOutputStream());
		new ClientListener(host.getInputStream(), this).start();
		new MoveDeliver(this).start();
	}
	
	public NetGame(String hostName, int port, MovementSource mvSource)
		throws UnknownHostException, IOException
	{
		status = GameStatus.NOT_STARTED;

		estabilishConnection(hostName, port);
		
		this.mvSource = mvSource;
		this.moveRequestActive = false;
	}
	
	@Override
	public Board getCurrentBoard()
	{
		return currentTurnInfo.getCurrentBoard();
	}

	@Override
	public PlayerColor getCurrentPlayer()
	{
		return currentTurnInfo.getCurrentPlayer();
	}

	@Override
	public String lastMoveError()
	{
		return currentTurnInfo.lastMoveError();
	}
	
	@Override
	public boolean gameFinished()
	{
		return !(status.equals(GameStatus.OK) || status.equals(GameStatus.NOT_STARTED));
	}

	@Override
	public boolean gameStarted()
	{
		return (currentTurnInfo == null ? false : currentTurnInfo.gameStarted());
	}

	@Override
	public int getTimeLeft(PlayerColor color)
	{
		return currentTurnInfo.getTimeLeft(color);
	}
	
	@Override
	public GameStatus getGameStatus()
	{
		return status;
	}
	
	@Override
	public ScoreBoard getFinalScores()
	{
		return currentTurnInfo.getFinalScores();
	}
	
	protected synchronized void updateStaus(GameInfo currentTurnInfo)
	{
		this.currentTurnInfo = currentTurnInfo;
		if(!currentTurnInfo.getGameStatus().equals(GameStatus.OK)
				&& !currentTurnInfo.getGameStatus().equals(GameStatus.NOT_STARTED)
				&& !currentTurnInfo.getGameStatus().equals(GameStatus.SUCCESSFULLY_ENDED))
			breakGame(currentTurnInfo.getGameStatus());
		notifyAll();
	}
	
	protected ClientDataSender getDataSender()
	{
		return this.dataSender;
	}
	
	protected synchronized void endGame(ScoreBoard finalScores)
	{
		status = GameStatus.SUCCESSFULLY_ENDED;
		notifyAll();
	}
	
	protected synchronized void sendMoveInfo()
	{
		moveRequestActive = true;
		notifyAll();
	}
	
	protected boolean isMoveRequestActive()
	{
		return this.moveRequestActive;
	}
	
	private class MoveDeliver extends Thread
	{
		private final NetGame parentNetGame;
		
		public MoveDeliver(NetGame parentNetGame)
		{
			this.setDaemon(true);
			this.parentNetGame = parentNetGame;
		}
		
		@Override
		public void run()
		{
			while(!parentNetGame.gameFinished())
			{
				if(parentNetGame.isMoveRequestActive())
				{
					Position newMove = parentNetGame.mvSource.getMove();
					if(newMove == null)
					{
						newMove = new Position(-1, -1);
					}
					try
					{
						parentNetGame.getDataSender().sendNewMove(newMove);
						parentNetGame.moveRequestActive = false;
					} catch (IOException e)
					{
						synchronized(parentNetGame)
						{
							parentNetGame.breakGame(GameStatus.CONNECTION_LOST);
							return;
						}
					}
					synchronized(parentNetGame)
					{
						try
						{
							parentNetGame.wait();
						} catch (InterruptedException e)
						{
							parentNetGame.breakGame(GameStatus.MOVE_DEILIVER_INTERRUPTION);
							return;
						}
					}
				} 
				else
					try
					{
						synchronized(parentNetGame){ parentNetGame.wait(); }
					} catch (InterruptedException e) { }
			}
		}
	}
	
	protected synchronized void startGame(PlayerColor playerColor, TransportableOneTurnGameInfo emptyInfo)
	{
		if(!status.equals(GameStatus.NOT_STARTED)) throw new IllegalStateException("Game already started");
		status = GameStatus.OK;
		currentTurnInfo = emptyInfo;
		notifyAll();
	}

	public synchronized void breakGame(GameStatus cause)
	{
		if(cause.equals(GameStatus.OK)) return;
		this.status = cause;
		notifyAll();
	}
}
